import { cp, createReadStream, createWriteStream, existsSync, lstatSync, mkdirSync, PathLike, readdirSync, statSync, symlink, symlinkSync, unlinkSync } from "fs";
import { Handler } from "./Handler";
import { basename, dirname, join, parse, relative } from "path";
import { Asset, CopyData, DirResult, FileResult } from "./Defines";
import { copyFile, rm } from "fs/promises";
import { Environment } from "./Environment";
import archiver from "archiver";
import { createHash } from "crypto";

class _FileUtils extends Handler {
    module: string = "【FileUtils】";

    private static _instance: _FileUtils = null!;
    static get instance() {
        return this._instance || (this._instance = new _FileUtils);
    }

    //#region 目录操作
    createDir(path: string) {
        // 分析路径，逐级创建目录
        if (!existsSync(path)) {
            let dirs = path.replace(/\\/g, "/").split("/");
            for (let i = 0; i < dirs.length; i++) {
                let dir = dirs.slice(0, i + 1).join("/");
                if (!existsSync(dir)) {
                    mkdirSync(dir);
                }
            }
        }
    }

    /**
     * @description 拷贝文件夹，把src文件夹下的所有文件和子文件夹拷贝到dest文件夹下，并返回拷贝的数据
     * @param src 源文件夹路径
     * @param dest 目标文件夹路径
     * @param datas 记录拷贝的数据
     * @returns 
     */
    private createCopyDatas(src: string, dest: string, datas: CopyData) {
        let stat = statSync(src);
        if (!stat.isDirectory()) {
            return;
        }

        this.createDir(dest);
        let from = "";
        let to = "";
        let create = (src: string, dest: string) => {
            let readDir = readdirSync(src);
            readDir.forEach((file) => {
                from = join(src, file);
                to = join(dest, file);
                let stat = statSync(from);
                if (stat.isDirectory()) {
                    this.createDir(to);
                    create(from, to);
                } else {
                    // 如果是文件，添加到数据中
                    datas.push({ from: from, to: to });
                }
            });
        }
        create(src, dest);
    }

    /**
     * @description 删除目录
     * @param path 要删除的目录路径
     */
    async delDir(path: PathLike) {
        if (existsSync(path)) {
            await rm(path, { recursive: true });
        }
    }

    /**
     * @description 复制目录
     * @param src 源目录路径
     * @param dest 目标目录路径
     */
    copyDir(src: string, dest: string) {
        return new Promise<boolean>(async resolve => {
            this.logger.log(`${this.module} 开始拷贝文件夹 ${src} -> ${dest}`);
            if (!existsSync(src)) {
                this.logger.error(`${this.module} 源文件夹 ${src} 不存在`);
                resolve(false);
                return;
            }
            await this.delDir(dest);
            if (Environment.isCommand && cp) {
                cp(src, dest, { recursive: true }, (err) => {
                    if (err) {
                        this.logger.error(`${this.module} 拷贝文件夹失败: ${err}`);
                        resolve(false);
                    } else {
                        this.logger.log(`${this.module} 拷贝文件夹成功`);
                        resolve(true);
                    }
                });
            } else {
                let datas: CopyData = [];
                this.createCopyDatas(src, dest, datas);
                for (let i = 0; i < datas.length; i++) {
                    let info = datas[i];
                    await this.copyFile(info.from, info.to);
                }
                resolve(true);
            }
        });
    }

    /**
     * @description 获取path下的所有目录
     * @param path 要获取目录的路径
     */
    getDirs(path: string) {
        let result: DirResult[] = [];
        if (!existsSync(path)) {
            return [];
        }

        let readDir = readdirSync(path);
        for (let i = 0; i < readDir.length; i++) {
            let file = readDir[i];
            let fullPath = join(path, file);
            if (fullPath[0] === ".") {
                continue;
            }
            let stat = statSync(fullPath);
            if (stat.isDirectory()) {
                result.push({ relative: relative(path, fullPath), path: fullPath, name: file });
            }
        }

        return result;
    }
    //#endregion

    //#region 文件操作
    /**@description     创建链接,把target链接到path
     * @param target 目标文件
     * @param path 链接文件
     * @param type 链接类型
     */
    async symlinkSync(target: PathLike, path: PathLike, type?: symlink.Type | null): Promise<void> {
        // target如果不存在，直接返回
        if (!existsSync(target)) {
            this.logger.error(`${this.module} ${target} 不存在`);
            return;
        }

        // 把path上已有的链接删除
        if (existsSync(path)) {
            //  lstat 返回的是链接文件本身的属性信息，而 stat 返回的是链接文件指向的文件的属性信息
            const stat = lstatSync(path);
            if (stat.isSymbolicLink()) {
                unlinkSync(path);
            }
        }

        // 如果target是文件夹，确保path有相同的目录结构
        let stat = statSync(target);
        if (stat.isDirectory()) {
            let root = parse(path as string);
            if (!existsSync(root.dir)) {
                this.createDir(root.dir);
            }
        }

        // 创建链接
        symlinkSync(target, path, type);
        this.logger.log(`${this.module} 创建链接 ${path} -> ${target}`);
    }

    /**
     * @description 获取目录下所有文件
     * @param path 
     * @param isInclude 是否包含该文件
     * @returns 
     */
    public getFiles(path: string, isInclude?: (info: FileResult) => boolean, root?: string, isCurrentDirFiles: boolean = false): FileResult[] {
        let files: FileResult[] = [];
        if (!root) {
            root = path;
        }
        this._getFiles(path, root, files, isInclude, isCurrentDirFiles);
        return files;
    }

    /**
     * @description 
     * @param path 
     * @param root 
     * @param result 
     * @param isInclude 
     * @param isCurrentDirFiles 是否只读取当前目录的文件
     * @returns 
     */
    private _getFiles(path: string, root: string, result: FileResult[], isInclude?: (info: FileResult) => boolean, isCurrentDirFiles: boolean = false) {
        if (!existsSync(path)) {
            // 结束递归
            return result;
        }

        // 读取文件夹
        let readDir = readdirSync(path);
        for (let i = 0; i < readDir.length; i++) {
            let file = readDir[i];
            let fullPath = join(path, file);
            if (fullPath[0] === ".") {
                continue;
            }
            let stat = statSync(fullPath);
            if (stat.isFile()) {
                let info: FileResult = { relative: relative(root, fullPath), path: fullPath, name: file, size: stat.size };
                if (isInclude) {
                    if (isInclude(info)) {
                        result.push(info);
                    }
                } else {
                    result.push(info);
                }
            } else {
                if (!isCurrentDirFiles) {
                    stat.isDirectory() && this._getFiles(fullPath, root, result, isInclude);
                }
            }
        }
    }

    /**
     * @description 获取当前目录下的所有文件
     * @param path 
     * @returns 
     */
    getCurFiles(path: string) {
        let result: FileResult[] = [];
        if (!existsSync(path)) {
            return result;
        }
        let readDir = readdirSync(path);
        for (let i = 0; i < readDir.length; i++) {
            let file = readDir[i];
            let fullPath = join(path, file);
            if (fullPath[0] === ".") {
                continue;
            }
            let stat = statSync(fullPath);
            if (stat.isFile()) {
                let info: FileResult = { relative: file, path: fullPath, name: file, size: stat.size };
                result.push(info);
            }
        }
        return result;
    }

    /**
     * 复制文件
     * @param src 
     * @param dest 
     * @param isForceCopy 是否强制复制，如果目标文件已存在，则删除目标文件后再复制 
     */
    async copyFile(src: string, dest: string, isForceCopy: boolean = false) {
        try {
            if (isForceCopy) {
                this.delFile(dest);
            }
            this.createDir(dirname(dest));
            await copyFile(src, dest);
        } catch (error) {
            this.logger.error(`${this.module} 复制文件失败: ${error}`);
        }
    }

    /**
     * @description 删除文件
     * @param path 要删除的文件路径
     * @returns 是否删除成功
     */
    delFile(path: PathLike) {
        if (existsSync(path)) {
            unlinkSync(path);
            return true;
        }
        return false;
    }

    /**
     * @description 计算目录下的文件数量
     * @param path 要计算的目录路径
     * @returns 文件数量
     */
    fileCount(path: string) {
        let count = 0;
        let counter = (path: string) => {
            let readDir = readdirSync(path);
            for (let i in readDir) {
                let fullPath = join(path, readDir[i]);
                if (statSync(fullPath).isDirectory()) {
                    counter(fullPath);
                } else {
                    count++;
                }
            }
        };
        counter(path);
        return count;
    }
    //#endregion
    /**
     * @description 格式化路径
     * @param path 
     * @returns 
     */
    formatPath(path: string) {
        // 替换所有的反斜杠为正斜杠
        path = path.replace(/\\/g, "/");
        // 去掉路径末尾的斜杠
        if (path.endsWith("/")) {
            path = path.slice(0, -1);
        }
        path = encodeURI(path);
        return path;
    }

    /**
     * @description 格式化文件路径
     * @param files 文件结果数组
     */
    formatPaths(files: FileResult[]) {
        for (let i = 0; i < files.length; i++) {
            files[i].relative = this.formatPath(files[i].relative);
        }
    }

    /**
     * @description 压缩文件
     * @param path 要压缩的文件或目录路径，可以是单个路径或多个路径的数组
     * @param outPath 输出的压缩文件路径
     * @param root 压缩包内的根目录
     * @param append 需要追加到压缩包中的文件结果数组
     */
    async archive(path: string | string[], outPath: string, root: string, append?: FileResult[]) {
        return new Promise<boolean>((resolve) => {
            let files: FileResult[] = [];
            if (typeof path === "string") {
                files = this.getFiles(path, undefined, root);
            } else {
                for (let i = 0; i < path.length; i++) {
                    let temp = this.getFiles(path[i], undefined, root);
                    files.push(...temp);
                }
            }
            if (append) {
                files.push(...append);
            }
            const arch = archiver("zip", {
                zlib: { level: 9 } // 设置压缩级别
            });
            const output = createWriteStream(outPath);

            arch.pipe(output);

            const addFileToArchive = async (files: FileResult[]) => {
                const batchSize = 50; // 每批添加的文件数量
                for (let i = 0; i < files.length; i += batchSize) {
                    const batch = files.slice(i, Math.min(i + batchSize, files.length));
                    await Promise.all(batch.map(file => {
                        return new Promise<void>((resolve) => {
                            const stream = createReadStream(file.path);
                            arch.append(stream, { name: file.relative });
                            stream.on("end", () => {
                                stream.destroy();
                                resolve();
                            });
                        });
                    }));
                }
            };

            arch.on("warning", (err) => {
                if (err.code !== "ENOENT") {
                    this.logger.warn(`${this.module} 压缩警告: ${err.message}`);
                }
            });

            arch.once("close", () => {
                this.logger.log(`${this.module} 压缩关闭: ${basename(outPath)}`);
            });

            arch.once("end", () => {
                this.logger.log(`${this.module} 压缩结束: ${basename(outPath)}`);
                resolve(true);
            });

            arch.once("error", (err) => {
                this.logger.error(`${this.module} 压缩错误: ${err.message}`);
                resolve(false);
            });
            this.logger.log(`${this.module} 开始压缩: ${basename(outPath)}`);
            addFileToArchive(files).then(() => arch.finalize());
        });
    }

    /**
     * @description 计算内容的MD5值
     * @param content 要计算MD5的内容，可以是字符串或Buffer
     * @returns MD5值
     */
    md5(content: string | Buffer) {
        return createHash("md5").update(content).digest("hex");
    }

    /**
     * @description 计算文件的MD5值
     */
    async md5File(fliePath: PathLike): Promise<string> {
        return new Promise<string>((resolve, reject) => {
            const hash = createHash("md5");
            const stream = createReadStream(fliePath);

            stream.on("data", (chunk) => {
                hash.update(chunk);
            });
            stream.on("end", () => {
                stream.destroy();
                resolve(hash.digest("hex"));
            });
            stream.on("error", (err) => {
                this.logger.error(`${this.module} 计算文件MD5失败: ${err.message}`);
                stream.destroy();
                reject(err);
            });
        });
    }

    /**
     * @description 计算目录下所有文件的MD5值，并将结果存储到 assets 对象中
     * @param path 要计算的目录路径
     * @param assets 存储文件信息的对象
     * @param root 根目录，用于计算相对路径
     * @param MainJS 可选参数，如果指定，则只计算该文件的MD5值
     */
    async md5Dir(path: string, assets: Asset, root: string, MainJS?: string) {
        let isCurrentDirFiles = MainJS != undefined;
        const files = FileUtils.getFiles(path, (info) => {
            if (MainJS) {
                return info.name == MainJS;
            }
            return true;
        }, root, isCurrentDirFiles);
        const conCurrentLimit = 50; // 并发限制

        // 将文件列表分批处理
        for (let i = 0; i < files.length; i += conCurrentLimit) {
            const batch = files.slice(i, Math.min(i + conCurrentLimit, files.length));
            try {
                // 批量处理当前批次的文件，计算MD5
                const results = await Promise.all(batch.map(async (file) => {
                    try {
                        const md5 = await this.md5File(file.path);
                        return {
                            relative: this.formatPath(file.relative),
                            size: file.size,
                            md5: md5
                        };
                    } catch (error) {
                        this.logger.error(`${this.module} 计算文件MD5失败: ${file.path}, 错误: ${error}`);
                        return null;
                    }
                }));

                // 更新 assets 对象
                results.forEach((result) => {
                    if (result) {
                        assets[result.relative] = { size: result.size, md5: result.md5 };
                    }
                });

            } catch (error) {
                this.logger.error(`${this.module} 计算文件MD5 批次处理失败: ${error}`);
            }
        }
    }
    //#region 工具

    //#endregion
}

export const FileUtils = _FileUtils.instance;